/***
* ==++==
*
* Copyright (c) Microsoft Corporation.  All rights reserved.
*
* ==--==
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* astream.h
*
* Asynchronous I/O: stream
*
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
****/
#pragma once

#include <astreambuf.h>

namespace async
{
    template<typename CharType>
    class basic_ostream
    {
    public:

        /// <summary>
        /// Default constructor
        /// </summary>
        basic_ostream() {}

        /// <summary>
        /// Copy constructor
        /// </summary>
        /// <param name="other">The source object</param>
        basic_ostream(const basic_ostream &other) : m_buffer(other.m_buffer) { }

        /// <summary>
        /// Assignment operator
        /// </summary>
        /// <param name="other">The source object</param>
        basic_ostream & operator =(const basic_ostream &other) { m_buffer = other.m_buffer; return *this; }

        /// <summary>
        /// Close the stream, preventing further write operations.
        /// </summary>
        pplx::task<bool> close() { return m_buffer.close(); }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="ptr">A stream buffer</param>
        basic_ostream(async::streambuf buffer) : m_buffer(buffer) { }

        /// <summary>
        /// Put a single character into the stream.
        /// </summary>
        /// <param name="ch">A character</param>
        pplx::task<int> put(CharType ch);

        /// <summary>
        /// Write a number of characters into the stream.
        /// </summary>
        /// <param name="ptr">A pointer to a memory block.</param>
        /// <param name="count">The number of characters to write.</param>
        pplx::task<size_t> write(const CharType *ptr, size_t count);

        /// <summary>
        /// Flush any buffered output data.
        /// </summary>
        pplx::task<int> flush();

        /// <summary>
        /// Seek to the specified write position.
        /// </summary>
        /// <param name="pos">An offset relative to the beginning of the stream.</param>
        pplx::task<size_t> seekp(size_t pos);

        /// <summary>
        /// Seek to the specified write position.
        /// </summary>
        /// <param name="off">An offset relative to the beginning, current write position, or the end of the stream.</param>
        /// <param name="way">The starting point (beginning, current, end) for the seek.</param>
        pplx::task<size_t> seekp(ptrdiff_t off, std::ios_base::seekdir way);

        /// <summary>
        /// Get the current write position, i.e. the offset from the beginning of the stream.
        /// </summary>
        pplx::task<size_t> tellp();

    protected:

        pplx::task<size_t> write(std::basic_string<char> str)
        {
            if ( str.size() > 0 )
                return m_buffer.aputn((const byte *)&str[0], str.size());
            else
                return pplx::create_value<size_t>(0);
        }

    private:
        friend pplx::task<async::basic_ostream<char>> operator << (async::basic_ostream<char> &os, const std::string& str);
        friend pplx::task<async::basic_ostream<wchar_t>> operator << (async::basic_ostream<wchar_t> &os, const std::wstring& str);

        async::streambuf m_buffer;
    };

    template<typename CharType>
    class basic_ofstream : public basic_ostream<CharType>
    {
    public:

         static pplx::task<basic_ostream> open(
            const std::string &file_name,
            std::ios_base::openmode mode = std::ios_base::out,
            int prot = (int)std::ios_base::_Openprot)
        {
            return async::fstreambuf::open(file_name, mode, prot)
                .then([](pplx::task<streambuf> op) -> basic_ostream
                {
                    auto buf = op.get();
                    return basic_ofstream<CharType>(buf);
                });
        }

        static pplx::task<basic_ostream> open(
            const std::wstring &file_name,
            std::ios_base::openmode mode = std::ios_base::out,
            int prot = (int)std::ios_base::_Openprot)
        {
            return async::fstreambuf::open(file_name, mode, prot)
                .then([](pplx::task<streambuf> op) -> basic_ostream
                {
                    auto buf = op.get();
                    return basic_ofstream(buf);
                });
        }

         /// <summary>
        /// Default constructor
        /// </summary>
        basic_ofstream() {}

        /// <summary>
        /// Copy constructor
        /// </summary>
        /// <param name="other">The source object</param>
        basic_ofstream(const basic_ofstream &other) : basic_ostream<CharType>(other) { }

        /// <summary>
        /// Assignment operator
        /// </summary>
        /// <param name="other">The source object</param>
        basic_ofstream & operator =(const basic_ofstream &other) { basic_ostream<CharType>::operator=(other); return *this; }

    private:
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="ptr">A stream buffer</param>
        basic_ofstream(async::streambuf buffer) : basic_ostream<CharType>(buffer) { }
    };

    typedef basic_ostream<char> ostream;
    typedef basic_ofstream<char> ofstream;

    typedef basic_ostream<wchar_t> wostream;
    typedef basic_ofstream<wchar_t> wofstream;

    inline
    pplx::task<async::basic_ostream<char>> operator << (async::basic_ostream<char> &os, const std::string& str)
    {
        return (str.size() == 0) 
            ? pplx::create_value(os) 
            : os.write(str).then(
                [os](pplx::task<size_t> op) -> async::basic_ostream<char>
                { 
                    op.get();
                    return os;
                });
    }

    inline 
    pplx::task<async::basic_ostream<wchar_t>> operator << (async::basic_ostream<wchar_t> &os, const std::wstring& str)
    {
        return (str.size() == 0) 
            ? pplx::create_value(os) 
            : os.write(utilities::conversions::utf16_to_utf8(str)).then(
                [os](pplx::task<size_t> op) -> async::basic_ostream<wchar_t>
                { 
                    op.get();
                    return os;
                });
    }

    template<typename T>
    pplx::task<async::ostream> operator << (async::basic_ostream<char> &os, const T& val)
    {
        std::ostringstream ss;
        ss << val;
        return os << ss.str();
    }

    template<typename T>
    pplx::task<async::wostream> operator << (async::wostream &os, const T& val)
    {
        std::wostringstream ss;
        ss << val;
        return os << ss.str();
    }

    template<typename T>
    pplx::task<async::basic_ostream<char>> operator << (pplx::task<async::basic_ostream<char>> os, const T& val)
    {
        std::ostringstream ss;
        ss << val;
        auto s = ss.str();

        return os.then(
            [s](pplx::task<async::basic_ostream<char>> op) -> pplx::task<async::basic_ostream<char>>
            {
                auto stream = op.get(); 
                return stream << s; 
            });
    }

    template<typename T>
    pplx::task<async::wostream> operator << (pplx::task<async::wostream> os, const T& val)
    {
        std::wostringstream ss;
        ss << val;
        auto s = ss.str();

        return os.then(
            [s](pplx::task<async::wostream> op) -> pplx::task<async::wostream>
            {
                auto stream = op.get(); 
                return stream << s; 
            });
    }

    template<typename CharType>
    pplx::task<int> basic_ostream<CharType>::put(CharType ch)
    {
        return m_buffer.aputc(ch);
    }

    template<typename CharType>
    pplx::task<size_t> basic_ostream<CharType>::write(const CharType *ptr, size_t count)
    {
        return m_buffer.aputn((const byte *)ptr, count*sizeof(CharType));
    }

    template<typename CharType>
    pplx::task<int> basic_ostream<CharType>::flush()
    {
        return m_buffer.sync();
    }

    template<typename CharType>
    pplx::task<size_t> async::basic_ostream<CharType>::seekp(size_t pos)
    {
        return m_buffer.aseekpos(pos, std::ios_base::out);
    }

    template<typename CharType>
    pplx::task<size_t> async::basic_ostream<CharType>::seekp(ptrdiff_t off, std::ios_base::seekdir way)
    {
        return m_buffer.aseekoff(off, way, std::ios_base::out);
    }

    template<typename CharType>
    pplx::task<size_t> basic_ostream<CharType>::tellp()
    {
        return m_buffer.aseekoff(0, std::ios_base::cur, std::ios_base::out);
    }
}